package cn.funnymap.lgis.tools.vector.generator;

import cn.funnymap.lgis.tools.exception.ShpGeneratorRuntimeException;
import cn.funnymap.lgis.tools.exception.ShpGeneratorValidationException;
import org.geotools.data.DataUtilities;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.*;

/**
 * Shapefile生成器
 *
 * @author jiaoxn
 */
public abstract class ShpGenerator {
    private static final Charset SHP_CHARSET = StandardCharsets.UTF_8;
    private GeneratorContent generatorContent;
    protected final Path shpFilePath;

    protected ShpGenerator(Path shpFilePath) {
        this.shpFilePath = shpFilePath;
    }

    public void run() {
        try {
            // 构建生成器可处理的内容
            this.generatorContent = this.buildGeneratorContent();

            // 数据校验
            this.validateGeneratorContent();

            // 定义Feature Type
            SimpleFeatureType simpleFeatureType = this.createFeatureType();

            // 创建数据存储
            ShapefileDataStore shapefileDataStore = this.createDataStore(shpFilePath, simpleFeatureType);

            // 数据集合
            SimpleFeatureCollection simpleFeatureCollection = this.createFeatureCollection(simpleFeatureType);

            // 数据写入到Shapefile文件
            this.writeData(shapefileDataStore, simpleFeatureCollection);
        } catch (IOException exception) {
            throw new ShpGeneratorRuntimeException("生成Shapefile文件时出错了，" + exception.getMessage());
        }
    }

    protected abstract GeneratorContent buildGeneratorContent();

    protected void validateGeneratorContent() {
        this.validateDataAndAttrValuesSize();
        this.validateUnifiedGeometryType();
        this.validateAttr();
    }
    private void validateDataAndAttrValuesSize() {
        if (generatorContent.getGeometryList().size() != generatorContent.getValueList().size()) {
            throw new ShpGeneratorValidationException("空间数据和属性数据的个数不一致，请检查输入数据");
        }
    }
    private void validateUnifiedGeometryType() {
        for (Geometry geometry : generatorContent.getGeometryList()) {
            if (!Objects.equals(geometry.getClass(), this.geometryType())) {
                throw new ShpGeneratorValidationException("空间数据中包含多种几何类型，请确保只有一种几何类型");
            }
        }
    }
    private void validateAttr() {
        int attrNumber = generatorContent.getFieldList().size();

        for (HashMap<String, Object> valueMap : generatorContent.getValueList()) {
            if (valueMap.keySet().size() != attrNumber) {
                throw new ShpGeneratorValidationException("属性数据的长度和属性定义的长度不一致，请检查输入数据");
            }

            valueMap.keySet()
                    .stream()
                    .filter(attrName -> !attributeValueTypeEqualDefinitionType(attrName, valueMap))
                    .forEachOrdered(attrName -> {
                        throw new ShpGeneratorValidationException(String.format("%s属性值与类型不一致", attrName));
                    });
        }
    }
    private boolean attributeValueTypeEqualDefinitionType(String fieldName, HashMap<String, Object> valueMap) {
        Object value = valueMap.get(fieldName);
        if (value != null) {
            ShpField shpField =
                    this.generatorContent.getFieldList()
                            .stream()
                            .filter(item -> Objects.equals(item.getName(), fieldName))
                            .findAny()
                            .orElse(null);
            if (shpField != null) {
                return whetherConvertAttributeValueToDefinitionType(value, shpField.getType());
            }
        }

        return false;
    }
    private boolean whetherConvertAttributeValueToDefinitionType(Object value, Class<?> attributeType) {
        if (value == null || attributeType == null) {
            return false;
        }

        Class<?> valueClass = value.getClass();
        if (valueClass == attributeType) {
            return true;
        } else {
            try {
                if (attributeType.equals(Integer.class)) {
                    Integer.parseInt(value.toString());
                } else if (attributeType.equals(Long.class)) {
                    Long.parseLong(value.toString());
                } else if (attributeType.equals(Float.class)) {
                    Float.parseFloat(value.toString());
                } else if (attributeType.equals(Double.class)) {
                    Double.parseDouble(value.toString());
                } else {
                    return false;
                }
            } catch (NumberFormatException exception) {
                return false;
            }
        }

        return true;
    }

    private SimpleFeatureType createFeatureType() {
        SimpleFeatureTypeBuilder simpleFeatureTypeBuilder = new SimpleFeatureTypeBuilder();
        simpleFeatureTypeBuilder.setCRS(this.generatorContent.getCrs());
        simpleFeatureTypeBuilder.setName("shapefile");
        simpleFeatureTypeBuilder.add("the_geom", this.geometryType());

        // 定义属性
        this.generatorContent.getFieldList().forEach(item -> simpleFeatureTypeBuilder.add(item.getName(), item.getType()));

        return simpleFeatureTypeBuilder.buildFeatureType();
    }
    protected abstract Class<?> geometryType();

    private ShapefileDataStore createDataStore(Path path, SimpleFeatureType simpleFeatureType) throws IOException {
        Map<String, Serializable> params = new HashMap<>();
        params.put(ShapefileDataStoreFactory.URLP.key, path.toUri().toURL());
        params.put("create spatial index", Boolean.TRUE);
        params.put(ShapefileDataStoreFactory.ENABLE_SPATIAL_INDEX.key, true);
        params.put(ShapefileDataStoreFactory.DBFCHARSET.key, SHP_CHARSET.name());

        ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();

        ShapefileDataStore shapefileDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
        shapefileDataStore.createSchema(simpleFeatureType);
        shapefileDataStore.setCharset(SHP_CHARSET);
        return shapefileDataStore;
    }

    private SimpleFeatureCollection createFeatureCollection(SimpleFeatureType featureType) {
        List<SimpleFeature> simpleFeatureList = new ArrayList<>();

        SimpleFeatureBuilder simpleFeatureBuilder = new SimpleFeatureBuilder(featureType);

        for (int i = 0; i < generatorContent.getGeometryList().size(); i++) {
            simpleFeatureBuilder.add(generatorContent.getGeometryList().get(i));
            for (Object attrValue : generatorContent.getValueList().get(i).values()) {
                simpleFeatureBuilder.add(attrValue);
            }
            SimpleFeature simpleFeature = simpleFeatureBuilder.buildFeature(null);
            simpleFeatureList.add(simpleFeature);
        }

        return DataUtilities.collection(simpleFeatureList);
    }

    private void writeData(ShapefileDataStore shapefileDataStore, SimpleFeatureCollection simpleFeatureCollection) throws IOException {
        String typeName = shapefileDataStore.getTypeNames()[0];
        SimpleFeatureSource simpleFeatureSource = shapefileDataStore.getFeatureSource(typeName);

        if (simpleFeatureSource instanceof SimpleFeatureStore) {
            SimpleFeatureStore simpleFeatureStore = (SimpleFeatureStore) simpleFeatureSource;

            try (Transaction transaction = Transaction.AUTO_COMMIT) {
                simpleFeatureStore.setTransaction(transaction);

                try {
                    simpleFeatureStore.addFeatures(simpleFeatureCollection);
                    transaction.commit();
                } catch (Exception exception) {
                    transaction.rollback();
                }
            }
        }
    }

    protected String cutFieldName(String str) {
        if (str == null) {
            return null;
        }

        int maxLength = 10;

        byte[] bytes = str.getBytes(SHP_CHARSET);
        if (bytes.length <= maxLength) {
            return str;
        }

        int bytesPerCn = "UTF-8".equals(SHP_CHARSET.name()) ? 3 : 2;
        byte[] newBytes = new byte[(maxLength / bytesPerCn) * bytesPerCn];
        int cursor = 0;
        while (cursor + bytesPerCn <= 10 ) {
            System.arraycopy(bytes, cursor, newBytes, cursor, bytesPerCn);
            cursor += bytesPerCn;
        }

        return new String(newBytes, SHP_CHARSET);
    }
}
